
本篇介紹 ES2020 (ES11) 提供的 BigInt。
本文同步發表於 Titangene Blog:JavaScript 之旅 (22):BigInt
「JavaScript 之旅」系列文章發文於:
Number在過去,只要 JavaScript 提到 primitive 就會想到 String、Number、Boolean,但其實還有在 ES6 提供的 Symbol,另外在 spec 的定義中還有 Undefined、Null。
除了以上這些,還有本篇要介紹的新 primitive:BigInt。
下面是 spec 對 primitive value 的定義:

Number 只能安全地表示最大為 $2^{53}$ 的整數,但有時會有大整數的需求,例如:
例如:在 JavaScript 竟然 2 ** 53 會等於 2 ** 53 + 1:
let x = 2 ** 53;
let y = 2 ** 53 + 1;
console.log(x);
// 9007199254740992
console.log(y);
// 9007199254740992
console.log(x === y);
// true
console.log(typeof x);
// number
console.log(typeof y);
// number
很奇怪吧!所以這就是 BigInt 被提出的原因!
為何會有這個現象是跟 IEEE 754 的浮點數定義有關,詳請可參閱 Daniel Ehrenberg 在 JSConf EU 2018 的 演講簡報 和 演講影片。
BigIntBigInt 是一種可用來表示 arbitrary precision (任意精度,高精度) 的整數,也就是可以表示大於 $2^{53}$ 的整數。
例如:剛剛 2 ** 53 和 2 ** 53 + 1 的例子若改用 BigInt 就會如你預期的不一樣 (本來就不同啊 XD):
let x = 2n ** 53n;
let y = 2n ** 53n + 1n;
console.log(x);
// 9007199254740992n
console.log(y);
// 9007199254740993n
console.log(x === y);
// false
console.log(typeof x);
// bigint
console.log(typeof y);
// bigint
BigInt 的語法和 Number 直接寫整數不太一樣,BigInt literal (字面值) 是在整數的後面加上 n 後綴:
let x = 9007199254740992n;
console.log(x);
//9007199254740992n
console.log(typeof x);
// bigint
另一種方式是呼叫 constructor 來建立 BigInt:
let x = BigInt(9007199254740992);
console.log(x);
//9007199254740992n
console.log(typeof x);
// bigint
BigInt 在 +、-、*、** 和 % 的運算上跟 Number 差不多 (這邊說的差不多是指整數小於 $2^{53}$ 的情況下):
let x = 9007199254740992n;
console.log(x + 1n);
// 9007199254740993n
console.log(x - 1n);
// 9007199254740991n
console.log(x * 2n);
// 18014398509481984n
console.log(x * -2n);
// -18014398509481984n
console.log(x ** 2n);
// 81129638414606681695789005144064n
console.log(x % 10n);
// 2n
但在小數運算上跟 Number 不同,因為 BigInt 不是 BigDecimals,所以會 rounded towards 0,即不保留任何小數 (類似無條件捨去):
console.log(5 / 2);
// 2.5
console.log(5n / 2n);
// 2n
console.log(5 / 3);
// 1.6666666666666667
console.log(5n / 3n);
// 1n
console.log(5 / 4);
// 1.25
console.log(5n / 4n);
// 1n
在 spec 中的 BigInt::divide ( x, y ) 就有定義 rounded towards 0:

BigInt 不嚴格等於 (not strictly equal) Number,例如:
console.log(0n === 0);
// false
console.log(0n == 0);
// true
在 spec 中的「Strict Equality Comparison」有定義:
false,即不嚴格等於Number 或 BigInt 就會回傳 Type(x)::equal(x, y)

若將上面範例中的 0n 帶入 spec,那 0n 就是 x,所以代表步驟 2 會去執行 BigInt::equal (x, y),spec 的定義如下:只要兩個值的數學整數值相等時,就會回傳 true,否則回傳 false

但可照常用 >、<、>= 和 <= 進行比較,例如:
console.log(3n > 2);
// true
console.log(3n < 2);
// false
console.log(2 < 3n);
// true
console.log(2 > 3n);
// false
console.log(2 < 2n);
// false
console.log(2 > 2n);
// false
console.log(2 <= 2n);
// true
console.log(2 >= 2n);
// true
若將 BigInt 強制轉型成 Boolean,行為會跟 Number 類似。
例如:if 陳述句
if (0) {
console.log('if')
} else {
console.log('else')
}
// else
if (0n) {
console.log('if')
} else {
console.log('else')
}
// else
if 陳述句會將值進行 ToBoolean() 強制轉型,spec 定義如下:

例如:二元邏輯運算子 (binary logical operator) 的 && 和 ||:
console.log(0 || 10);
// 10
console.log(0 && 10);
// 0
console.log(0n || 10n);
// 10n
console.log(0n && 10n);
// 0n
&& 和 || 會將值進行 ToBoolean() 強制轉型,spec 定義如下:

例如:使用 Boolean() constructor:
console.log(Boolean(0));
// false
console.log(Boolean(10));
// true
console.log(Boolean(0n));
// false
console.log(Boolean(10n));
// true
Boolean() constructor 會將值進行 ToBoolean() 強制轉型,spec 定義如下:

例如:邏輯 NOT 運算子 (logical NOT operator) !:
console.log(!0);
// true
console.log(!10);
// false
console.log(!0n);
// true
console.log(!10n);
// false
! 會將值進行 ToBoolean() 強制轉型,spec 定義如下:

上面說的這些都將值進行 ToBoolean() 強制轉型,下面是 ToBoolean() 的定義:只有 0n 會回傳 false,其他都回傳 true (跟 Number 很像對吧?有興趣的可以看 Number 的部份)

所以這就是 0n 會變成 false 的原因!
BigInt 不能和 unary + 運算子一起使用,需先用 Number() constructor 轉換:
console.log(+1n);
// TypeError: Cannot convert a BigInt value to a number
console.log(Number(1n));
// 1
在 spec 是有定義的,因為 unary + 運算子會進行 ToNumber() 的強制轉型:

但 ToNumber() 只要遇到 BigInt 就會拋出 TypeError exception:

Number 一起使用不能和 Number 一起使用,否則會拋出 TypeError:
console.log(1n + 2);
// TypeError: Cannot mix BigInt and other types, use explicit conversions
console.log(1n * 2);
// TypeError: Cannot mix BigInt and other types, use explicit conversions
若要和 Number 一起使用,請先用 Number() constructor 強制轉型 (即 explicit conversions) 成 Number:
console.log(Number(1n) + 2);
// 3
console.log(Number(1n) * 2);
// 2
下面是 + 運算子在 spec 中的定義,其中的步驟 8 和 9 都會將 + 左右邊的兩個運算元進行 ToNumeric() 的強制轉型:

而下面是 ToNumeric() 的定義,可以看到步驟 2,只要值為 BigInt 就直接回傳值,所以不會跑到步驟 3:

步驟 1 的
ToPrimitive()只會對型別為Object的值才會有特殊行為,所以本篇會跳過這邊的細節 XD,否則篇幅會更長,在這裡可以簡單當作將BigInt型別的值傳給ToPrimitive()會回傳一樣的值,不會有任何強制轉型的行為。
接著回到 + 運算子的步驟 (圖重貼一次):
Number,另一個為 BigInt (因為前面的範例是 1n + 2 )TypeError exception
所以這就是 BigInt 不能和 Number 一起使用的原因。
String 進行串接例如:
console.log(1n + '2');
// 12
console.log('2' + 1n);
// 21
BigInt() constructor 不能傳入 Number 或 String 型別的小數例如:
console.log(BigInt(2.5));
// RangeError: The number 2.5 cannot be converted to a BigInt because it is not an integer
console.log(BigInt('2.5'));
// SyntaxError: Cannot convert 2.5 to a BigInt
下面是 BigInt() constructor 在 spec 的定義:
ToPrimitive() 強制轉型Number,則進行 NumberToBigInt(prim) 強制轉型ToBigInt() 強制轉型
先來看為何不能傳入 Number 型別的小數?這跟步驟 3 有關,會進行 NumberToBigInt(prim) 強制轉型,spec 的定義如下:
RangeError exception
那為何不能傳入 String 型別的小數?這跟步驟 4 有關,會進行 ToBigInt() 強制轉型,spec 的定義如下:
StringToBigInt() 強制轉型NaN,會拋出 SyntaxError exception
而下面是 StringToBigInt() 的定義:
Infinity、小數點或指數NaN,則回傳 NaN,否則回傳與 MV 完全對應的 BigInt,而不是四捨五入成 Number

所以字串會透過 StringToBigInt() 強制轉行成 NaN,就會拋出 SyntaxError exception。